/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {
    AbstractExpr,
    ArkAssignStmt,
    ArkFile,
    ArkInstanceFieldRef,
    ArkNewExpr,
    ArkNormalBinopExpr,
    ArkStaticFieldRef,
    ClassSignature,
    ClassType,
    Constant,
    EnumValueType,
    ImportInfo,
    Local,
    Stmt,
    StringType,
    UnionType,
    Value
} from 'arkanalyzer';
import {ArkClass, ClassCategory} from 'arkanalyzer/lib/core/model/ArkClass';
import {VarInfo} from '../../model/VarInfo';
import {StmtExt} from '../../model/StmtExt';
import {CheckerStorage} from '../common/CheckerStorage';
import {Scope} from '../../model/Scope';
import {CheckerUtils} from './CheckerUtils';
import {ArkNewArrayExpr} from 'arkanalyzer/lib';

export class StringUtils {
    public static getStringByScope(arkFile: ArkFile, valueStmtInfo: VarInfo, value: Value): string {
        if (value instanceof Constant) {
            return value.getValue();
        } else if (value instanceof Local) {
            if (!value.toString().includes('%')) {
                let importInfo = this.getValueImportInfo(arkFile, value);
                if (importInfo) {
                    return this.getImportStringValue(importInfo, value);
                } else {
                    return this.getMethodStringValue(arkFile, valueStmtInfo, value);
                }
            } else {
                let declaringStmt = value.getDeclaringStmt();
                if (!declaringStmt) {
                    return '';
                }
                let tmpStmt = declaringStmt;
                if (!(declaringStmt instanceof ArkAssignStmt)) {
                    return '';
                }
                let rightOp = declaringStmt.getRightOp();
                valueStmtInfo.stmt = declaringStmt;
                valueStmtInfo.scope = (tmpStmt as StmtExt).scope;
                return this.getStringByScope(arkFile, valueStmtInfo, rightOp);
            }
        } else if (value instanceof AbstractExpr && value.getType() instanceof StringType) {
            return this.getExprStringValue(arkFile, valueStmtInfo, value);
        } else if (value instanceof ArkStaticFieldRef && value.getType() instanceof StringType) {
            return this.getStaticStringValue(arkFile, value);
        } else if (value instanceof ArkStaticFieldRef && value.getType() instanceof ClassType) {
            return this.getStaticStringValue(arkFile, value);
        } else if (value instanceof ArkStaticFieldRef && value.getType() instanceof EnumValueType) {
            return this.getStaticStringValue(arkFile, value);
        } else if (value instanceof ArkInstanceFieldRef) {
            return this.getInstanceFieldValue(arkFile, valueStmtInfo, value);
        }
        return '';
    }

    private static getInstanceFieldValue(arkFile: ArkFile, valueStmtInfo: VarInfo, value: ArkInstanceFieldRef): string {
        let fieldSignature = value.getFieldSignature();
        for (let clazz of arkFile.getClasses()) {
            let field = clazz.getField(fieldSignature);
            if (!field) {
                continue;
            }
            let initializer = field.getInitializer()[0];
            if (!initializer) {
                continue;
            }
            if (!(initializer instanceof ArkAssignStmt)) {
                continue;
            }
            return this.getStringByScope(arkFile, valueStmtInfo, initializer.getRightOp());
        }
        return '';
    }

    private static getValueImportInfo(arkFile: ArkFile, value: Local): ImportInfo | undefined {
        let importInfos = arkFile.getImportInfos();
        for (let importInfo of importInfos) {
            if (importInfo.getImportClauseName() === value.getName()) {
                return importInfo;
            }
        }
        return undefined;
    }

    private static getImportStringValue(importInfo: ImportInfo, value: Local): string {
        let exportInfo = importInfo.getLazyExportInfo();
        let importArkFile = exportInfo?.getDeclaringArkFile();
        if (!importArkFile) {
            return '';
        }
        let scope = CheckerStorage.getInstance().getScope(importArkFile.getFilePath());
        if (!scope) {
            return '';
        }
        for (let varDef of scope.defList) {
            if (varDef.getName() !== value.getName()) {
                continue;
            }
            let stmt = varDef.defStmt;
            const text = stmt.getOriginalText();
            if (!text || text.length === 0) {
                continue;
            }
            if (stmt instanceof ArkAssignStmt) {
                let defStmtInfo = new VarInfo(stmt, scope);
                let rightOp = stmt.getRightOp();
                return this.getStringByScope(importArkFile, defStmtInfo, rightOp);
            }
        }
        return '';
    }

    private static getMethodStringValue(arkFile: ArkFile, valueStmtInfo: VarInfo, value: Local): string {
        let scope: Scope = valueStmtInfo.scope;
        let valueStmt: Stmt = valueStmtInfo.stmt;
        let hasFind = false;
        if (!scope || !scope.defList) {
            return '';
        }
        let defLists = scope.defList;
        for (let defVar of defLists) {
            if (defVar.getName() !== value.getName()) {
                continue;
            }
            hasFind = true;
            let nearReDefStmtInfo = new VarInfo(defVar.defStmt, scope);
            let reDefStmtInfos = defVar.redefInfo;
            for (let reDefStmtInfo of reDefStmtInfos) {
                let originalLine = valueStmt.getOriginPositionInfo().getLineNo();
                let redefLine = reDefStmtInfo.stmt.getOriginPositionInfo().getLineNo();
                if (redefLine >= originalLine) {
                    break;
                }
                nearReDefStmtInfo = reDefStmtInfo;
            }
            let stmt = nearReDefStmtInfo.stmt;
            if (stmt instanceof ArkAssignStmt) {
                let rightOp = stmt.getRightOp();
                return this.getStringByScope(arkFile, nearReDefStmtInfo, rightOp);
            }
        }
        if (!hasFind && scope.parentScope !== null) {
            let defStmtInfo = new VarInfo(valueStmt, scope.parentScope);
            return this.getStringByScope(arkFile, defStmtInfo, value);
        }
        return '';
    }

    private static getExprStringValue(arkFile: ArkFile, stmtInfo: VarInfo, value: AbstractExpr): string {
        if (value instanceof ArkNormalBinopExpr) {
            let stringOfOp1 = this.getStringByScope(arkFile, stmtInfo, value.getOp1());
            let stringOfOp2 = this.getStringByScope(arkFile, stmtInfo, value.getOp2());
            switch (value.getOperator()) {
                case '+':
                    return stringOfOp1 + stringOfOp2;
            }
        }
        return '';
    }

    private static getStaticStringValue(arkFile: ArkFile, value: ArkStaticFieldRef): string {
        let classSignature = value.getFieldSignature().getDeclaringSignature();
        if (!(classSignature instanceof ClassSignature)) {
            return '';
        }
        let fieldSignature = classSignature.getDeclaringFileSignature();
        let staticClassArkFile = arkFile.getScene().getFile(fieldSignature);
        if (!staticClassArkFile) {
            return '';
        }
        let staticClass = staticClassArkFile.getClass(classSignature);
        if (!staticClass) {
            return '';
        }
        let staticField = staticClass.getStaticFieldWithName(value.getFieldName());
        if (!staticField) {
            return '';
        }
        if (staticClass.getCategory() === ClassCategory.CLASS && !staticField.isReadonly()) {
            return '';
        }
        let stmts = staticField.getInitializer();
        if (stmts.length === 0) {
            return '';
        }
        let stmt = stmts[0];
        let varInfo = new VarInfo(stmt, (stmt as StmtExt).scope);
        if (!(stmt instanceof ArkAssignStmt)) {
            return '';
        }
        let initValue = stmt.getRightOp();
        return this.getStringByScope(staticClassArkFile, varInfo, initValue);
    }

    public static getHuksString(arkClass: ArkClass, valueStmtInfo: VarInfo, value: Value): string {
        const arkFile = arkClass.getDeclaringArkFile();
        if (value instanceof Constant) {
            return value.getValue();
        } else if (value instanceof Local) {
            if (!value.toString().includes('%')) {
                let importInfo = this.getValueImportInfo(arkFile, value);
                if (importInfo) {
                    return this.getImportStringValue(importInfo, value);
                } else {
                    return this.getMethodStringValue(arkFile, valueStmtInfo, value);
                }
            } else {
                let declaringStmt = value.getDeclaringStmt();
                if (!declaringStmt) {
                    return '';
                }
                let tmpStmt = declaringStmt;
                if (!(declaringStmt instanceof ArkAssignStmt)) {
                    return '';
                }
                let rightOp = declaringStmt.getRightOp();
                valueStmtInfo.stmt = declaringStmt;
                valueStmtInfo.scope = (tmpStmt as StmtExt).scope;
                return this.getHuksString(arkClass, valueStmtInfo, rightOp);
            }
        } else if (value instanceof ArkNewExpr) {
            return this.getArkNewExpr(arkFile, value);
        }
        return '';
    }

    private static getArkNewExpr(arkFile: ArkFile, value: ArkNewExpr): string {
        const classSignature = value.getClassType().getClassSignature();
        const arkClass = arkFile.getClass(classSignature);
        if (arkClass != null) {
            const arkFields = arkClass.getFields();
            const initializer = arkFields[0].getInitializer();
            const stmt1 = initializer[initializer.length - 1];
            if (stmt1 instanceof ArkAssignStmt) {
                const rightOp1 = stmt1.getRightOp();
                if (rightOp1 instanceof Local) {
                    const declaringStmt1 = rightOp1.getDeclaringStmt();
                    return declaringStmt1 == null ? this.getStmt(rightOp1, arkClass, arkFile) : declaringStmt1 instanceof ArkAssignStmt ? this.getArkAssignStmt(declaringStmt1, arkFile) : '';
                }
            }
        }
        return '';
    }

    private static getStmt(rightOp: Local, arkClass: ArkClass, arkFile: ArkFile): string {
        const defaultArkMethod = arkClass.getDeclaringArkFile().getDefaultClass().getDefaultArkMethod();
        const locals = defaultArkMethod?.getBody()?.getLocals();
        const local = locals?.get(rightOp.getName());
        const declaringStmt = local?.getDeclaringStmt();
        if (declaringStmt) {
            rightOp.setDeclaringStmt(declaringStmt);
        }
        let rightOp1 = CheckerUtils.getArgRight(rightOp, arkClass);
        const declaringStmt2 = rightOp1 instanceof Local ? rightOp1.getDeclaringStmt() : null;
        const rightOp3 = declaringStmt2 instanceof ArkAssignStmt ? declaringStmt2.getRightOp() : null;
        const baseType = rightOp3 instanceof ArkNewArrayExpr ? rightOp3.getBaseType() : null;
        const types = baseType instanceof UnionType ? baseType.getTypes() : null;
        if (types) {
            for (let type of types) {
                const classSignature1 = type instanceof ClassType ? type.getClassSignature() : null;
                const class1 = classSignature1 == null ? null : arkFile.getClass(classSignature1);
                const fields = class1?.getFields();
                const code = fields === undefined ? null : fields[1].getCode();
                const strings = code?.split('.');
                if (strings != null && (strings[1] === 'HuksCipherMode' || strings[1] === 'HuksKeyDigest' || strings[1] === 'HuksKeyPadding')) {
                    return strings[2];
                }
            }
        }
        return '';
    }

    public static getArkAssignStmt(declaringStmt: ArkAssignStmt, arkFile: ArkFile): string {
        const rightOp = declaringStmt.getRightOp();
        const type = rightOp instanceof ArkInstanceFieldRef ? rightOp.getBase().getType() : undefined;
        const classSignature = type instanceof ClassType ? type.getClassSignature() : undefined;
        const clazz = classSignature !== undefined ? arkFile.getClass(classSignature) : null;
        const fields = clazz?.getFields();
        const initializer1 = fields !== undefined ? fields[0].getInitializer() : undefined;
        if (initializer1 !== undefined) {
            for (let stmt of initializer1) {
                const types = stmt instanceof ArkAssignStmt ? stmt.getRightOp().getType() : undefined;
                const classSignatures = types instanceof ClassType ? types.getClassSignature() : undefined;
                const clazzs = classSignatures !== undefined ? arkFile.getClass(classSignatures) : undefined;
                const fieldes = clazzs?.getFields();
                const code = fieldes === undefined ? null : fieldes[1].getCode();
                const strings = code?.split('.');
                if (strings != null && (strings[1] === 'HuksCipherMode' || strings[1] === 'HuksKeyDigest' || strings[1] === 'HuksKeyPadding')) {
                    return strings[2];
                }
            }
        }
        return '';
    }
}